iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Web 3

Web3新手初探筆記系列 第 20

實作簡易DEX ep.3

  • 分享至 

  • xImage
  •  

計算滑點

x=代幣x的量,y=代幣y的量,dx=投入代幣x的量,dy=輸出代幣y的量,dy’=零滑點輸出代幣y的量

實際兌換

https://chart.googleapis.com/chart?cht=tx&chl=dy%20%3D%7Bdx(y)%5Cover%20x%2Bdx%7D

零滑點兌換

https://chart.googleapis.com/chart?cht=tx&chl=dy'%3D%7Bdx(y)%5Cover%20x%7D

滑點計算z

https://chart.googleapis.com/chart?cht=tx&chl=z%3D%7Bdy%5Cover%20dy'%7D
這裡寫一個函式其會返回實際兌換和零滑點兌換以及萬分之多少的滑點

function caculateSlip (uint dx, uint x, uint y) external pure returns (uint, uint, uint){
    //實際兌換
	uint dy1 = dx * y / (x + dx);
	//零滑點
    uint dy0 = dx * y / x;
	//增加精準度
    uint slip = dy1 * 10000 / dy0;
    uint z = 10000 - slip;
    return (dy1,dy0,z);
}

降低滑點

Solidity中沒有小數,建議使用較大數字測試準確度較高,位數小不容易計算準確度

1.池裡量夠大

x/y=9/11 價格不變的情況下:

當輸入x=900000 y=1100000 dx=50000 得出 dy1=57894 dy0=611111 z=527 滑點約為5.3%

輸入x=90000000 y=11000000 dx=50000 得出 dy1=60773 dy0=61111 z=56 滑點減少為0.6%

可以看出當池子的代幣越多,其xy=k值就越大,則拋物線會越接近於直線,相同的dx滑點就越小。

2.兩個池的價格比例接近一比一

xy=144000000000000 流動性不變的情況下:

當輸入x=900000 y=160000000 dx=500 得出 dy1=8421052 dy0=8888888 z=527 滑點為5.3%

輸入x=9000000 y=16000000 dx=50000 得出 dy1=88397 dy0=88888 z=56 滑點減少為0.6%

輸入x=12000000 y=12000000 dx=50000 得出 dy1=49792 dy0=50000 z=42 滑點減少為0.4%

總結

將前面的所有function合起來

以下是V2版的CPMM

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./ERC20_LPtoken.sol";

contract AMM{
    //第一個address是LPtoken地址 第二個地址是池子代幣資產量
    mapping (address => mapping(address => uint)) public reserve;
    //查詢對應的LPtoken地址
    mapping (address => mapping(address => address)) public checkLPtokenAddress;
    //LPtoken地址列
    address [] public LPtokenAddressList;

    //建立池子
    function createLptokenPair(address token0, address token1) internal returns(address){
        bytes32 _salt = keccak256(
            abi.encodePacked(
                token0, token1
            )
        );
        LPtoken lpToken = new LPtoken{
            salt:bytes32(_salt)
        }();
        address LPtokenAddress = address(lpToken);
        //添加LPtoken地址
        checkLPtokenAddress[token0][token1] = LPtokenAddress;
        checkLPtokenAddress[token1][token0] = LPtokenAddress;
        LPtokenAddressList.push(LPtokenAddress);
        return LPtokenAddress;
    }
    //添加流動性
    function addLiquidity (address _token0, address _token1, uint _amount0, uint _amount1) external returns (uint shares){
        //地址不能相同
        require(_token0 != _token1,"Address can not be the same");
        //地址不能為零
        require(_token0 != address(0) && _token1 != address(0),"Address can not be 0x00");
        //確認池子對是否已存在
        if(checkLPtokenAddress[_token0][_token1] == address(0) && checkLPtokenAddress[_token1][_token0] == address(0)){
            //創建新池子對的LPtoken地址
            address LPtokenAddress = createLptokenPair(_token0, _token1);
            LPtoken lpTokenInstance = LPtoken(LPtokenAddress);
            //轉移ERC20
            ERC20 token0 = ERC20(_token0);
            ERC20 token1 = ERC20(_token1);
            token0.transferFrom(msg.sender, address(this), _amount0);
            token1.transferFrom(msg.sender, address(this), _amount1);
            //添加池子
            reserve[LPtokenAddress][_token0] += _amount0;
            reserve[LPtokenAddress][_token1] += _amount1;
            //返回獎勵
            shares = _sqrt(_amount0 * _amount1);
            //鑄造獎勵代幣
            lpTokenInstance.mint(msg.sender,shares);
        }else{
            //LPtoken的地址
            address LPtokenAddress = checkLPtokenAddress[_token1][_token0];
            LPtoken lpTokenInstance = LPtoken(LPtokenAddress);
            //LPtoken的總數量
            uint totalSupply = lpTokenInstance.totalSupply();
            //限制投入比例
            uint reserve0 = reserve[LPtokenAddress][_token0];
            uint reserve1 = reserve[LPtokenAddress][_token1];
            require(_amount1 * reserve0 == _amount0 * reserve1,"Investment is not proportional");
            //轉移ERC20
            ERC20 token0 = ERC20(_token0);
            ERC20 token1 = ERC20(_token1);
            token0.transferFrom(msg.sender, address(this), _amount0);
            token1.transferFrom(msg.sender, address(this), _amount1);
            //添加池子
            reserve[LPtokenAddress][_token0] += _amount0;
            reserve[LPtokenAddress][_token1] += _amount1;
            //返回獎勵
            shares = (_amount0 * totalSupply)/reserve0;
            //鑄造獎勵代幣
            lpTokenInstance.mint(msg.sender,shares);
        }
    }
    //減少流動性
    function minusLiquidity (address _token0, address _token1, uint _amountLp) external returns (uint _amount0, uint _amount1){
        //LPtoken池子地址
        address LPtokenAddress = checkLPtokenAddress[_token1][_token0];
        LPtoken lpTokenInstance = LPtoken(LPtokenAddress);
        //LPtoken的總數量
        uint totalSupply = lpTokenInstance.totalSupply();
        //返回數量
        uint reserve0 = reserve[LPtokenAddress][_token0];
        uint reserve1 = reserve[LPtokenAddress][_token1];
        _amount0 = (_amountLp * reserve0) / totalSupply;
        _amount1 = (_amountLp * reserve1) / totalSupply;
        //轉移ERC20
        ERC20 token0 = ERC20(_token0);
        ERC20 token1 = ERC20(_token1);
        token0.transfer(msg.sender, _amount0);
        token1.transfer(msg.sender, _amount1);
        //減少池子
        reserve[LPtokenAddress][_token0] -= _amount0;
        reserve[LPtokenAddress][_token1] -= _amount1;
        //燒毀LPtoken
        lpTokenInstance.burn(msg.sender, _amountLp);
    }
    //兌換
    function swap (address _tokenIn, address _tokenOut, uint _amountIn) external returns (uint _amountOut){
        //LPtoken池子地址
        address LPtokenAddress = checkLPtokenAddress[_tokenIn][_tokenOut];
        //返回數量
        uint reserve0 = reserve[LPtokenAddress][_tokenIn];
        uint reserve1 = reserve[LPtokenAddress][_tokenOut];
        _amountOut = _amountIn * reserve1 / (_amountIn + reserve0); 
        //轉移ERC20
        ERC20 token0 = ERC20(_tokenIn);
        ERC20 token1 = ERC20(_tokenOut);
        token0.transferFrom(msg.sender, address(this), _amountIn);
        token1.transfer(msg.sender, _amountOut);
        //池內數量調整
        reserve[LPtokenAddress][_tokenIn] += _amountIn;
        reserve[LPtokenAddress][_tokenOut] -= _amountOut;
    }


    //計算滑點
    function caculateSlip (uint dx, uint x, uint y) external pure returns (uint, uint, uint){
        uint dy1 = dx * y / (x + dx);
        uint dy0 = dx * y / x;
        uint slip = dy1 * 10000 / dy0;
        uint ans = 10000 - slip;
        return (dy1,dy0,ans);
    }
    //計算兌換
    function caculateAmountOut (uint _amountIn, uint _reserveIn, uint _reserveOut) external pure returns (uint){
        return _reserveOut * _amountIn / (_amountIn + _reserveIn);
    }
    //開根號函式
    function _sqrt(uint y) private pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}

上一篇
實作簡易DEX ep.2
下一篇
實作簡易多簽錢包
系列文
Web3新手初探筆記32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言